use crate::{
    board::Board,
    geometry::Dir,
    utils::{choose_by_weight, make_rng},
};
use itertools::Itertools;
use rand::seq::SliceRandom;
use rand_pcg::Pcg64Mcg;

pub fn hint(b: &Board<()>) -> Option<Dir> {
    let rng = &mut make_rng();
    Dir::EACH
        .into_iter()
        .filter(|&dir| b.swipe_valid(dir))
        .max_by_key(|&dir| {
            let mut b = b.clone();
            b.swipe(dir, &mut ());
            b.possible_spawn()
                .map(|(s, _)| {
                    let mut b = b.clone();
                    b.do_spawn(s, &mut ());
                    eval(b, rng)
                })
                .min()
                .unwrap_or_else(|| eval(b, rng))
        })
}

fn eval(b: Board<()>, rng: &mut Pcg64Mcg) -> u64 {
    Dir::EACH
        .into_iter()
        .filter(|&dir| b.swipe_valid(dir))
        .map(|dir| {
            let mut b = b.clone();
            b.swipe(dir, &mut ());
            eval1(b, rng)
        })
        .max()
        .unwrap_or(0)
}

fn eval1(b: Board<()>, rng: &mut Pcg64Mcg) -> u64 {
    let empty = b.iter_cells().filter(Option::is_none).count() as u64;
    let shape = Dir::EACH.into_iter().map(|dir| {
        let cells = b.cells();
        let mut lines = cells
            .iter_lines(dir)
            .map(|line| line.flatten().map(|(l, _)| l.get()).sum::<u64>());
        lines.next_tuple().map_or(0, |(l1, l2)| l1 * 60 + l2 * 5)
    });
    let shape = shape.max().unwrap();
    let simu: u64 = (0..10).map(|_| simu(b.clone(), rng)).sum();
    empty * 110 + shape + simu
}

fn simu(mut b: Board<()>, rng: &mut Pcg64Mcg) -> u64 {
    for i in 0..30 {
        let mut dirs = Dir::EACH;
        dirs.shuffle(rng);

        let Some(dir) = dirs.into_iter().find(|&dir| b.swipe_valid(dir)) else {
            return b.score() + i * 30;
        };
        b.swipe(dir, &mut ());

        if let Some(s) = choose_by_weight(rng, b.possible_spawn()) {
            b.do_spawn(s, &mut ());
        }
    }
    b.score() + 1000
}
